
package network;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.http.AndroidHttpClient;
import android.text.TextUtils;
import android.util.Log;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;

public class NetConnectClient {

    private static final String TAG = "NetConnectClient";

    /**
     * default connect times
     */
    private static final int DEFAULT_TRY_NUM = 3;

    /**
     * default connect timeout
     */
    private static final int SOCKET_OPERATION_TIMEOUT = 20 * 1000;

    /**
     * request type: get or post
     */
    public static final int CONNECT_TYPE_HTTPCLIENT_GET = 0;
    public static final int CONNECT_TYPE_HTTPCLIENT_POST = 1;

    public static final String USER_AGENT_HTTP = "wangzhenyu(Android)";
    public static final String USER_AGENT_WEB = "wangzhenyu(Android) web";
    public static final String KEY_MULTI_VALUES = "multi";

    private Context mContext = null;

    private NetConnectResponse mResponedCallback = null;

    private HttpClient mHttpClient = null;

    private int mTryNum = DEFAULT_TRY_NUM;

    private boolean mDisconnected = false;

    private int mTimeOut = SOCKET_OPERATION_TIMEOUT;

    private AppendInfo mAppendInfo;

    private List<String> mMultiKeyValues;

    /**
     * @param context
     * @param httpResponed
     */
    public NetConnectClient(Context context, NetConnectResponse httpResponed) {
        this(context, httpResponed, SOCKET_OPERATION_TIMEOUT);
    }

    /**
     * @param context
     * @param httpResponed
     * @param timeout
     */
    public NetConnectClient(Context context, NetConnectResponse httpResponed, int timeout) {

        mContext = context;
        mDisconnected = false;
        mResponedCallback = httpResponed;
        mTimeOut = timeout;
        mHttpClient = getHttpClient();
    }

    /**
     * set connect times
     *
     * @param tryNum
     */
    public void setTryNum(int tryNum) {

        mTryNum = tryNum;
    }

    /**
     * append request info
     *
     * @param appendInfo
     */
    public void setAppendInfo(AppendInfo appendInfo) {

        mAppendInfo = appendInfo;
    }

    public void setMultiKeyValues(List<String> values) {

        this.mMultiKeyValues = values;
    }

    private HttpClient getHttpClient() {

        DefaultHttpClient httpClient = null;
        BasicHttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, mTimeOut);
        HttpConnectionParams.setSoTimeout(httpParams, mTimeOut);
        HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
        HttpClientParams.setRedirecting(httpParams, true);
        HttpProtocolParams.setUserAgent(httpParams, USER_AGENT_HTTP);
        httpClient = new DefaultHttpClient(httpParams);

        if (httpClient != null) {
            DefaultHttpRequestRetryHandler retryhandler = new DefaultHttpRequestRetryHandler(0, false);
            httpClient.setHttpRequestRetryHandler(retryhandler);
        }

        return httpClient;
    }

    public void disconnect() {

        if (mHttpClient != null) {

            if (mHttpClient instanceof DefaultHttpClient) {
                mHttpClient.getConnectionManager().shutdown();
            } else if (mHttpClient instanceof AndroidHttpClient) {
                ((AndroidHttpClient) mHttpClient).close();
            }
        }

        mDisconnected = true;
        mHttpClient = null;
    }

    /**
     * try to connect net
     *
     * @param url:    request url
     * @param params: request params
     * @param method: request type: get or post
     */
    public void connect(String url, HashMap<String, String> params, int method) {

        if (TextUtils.isEmpty(url)) {
            throw new RuntimeException("connect url can't be url");
        }

        if (mAppendInfo != null) {
            url = mAppendInfo.onAppendRequestInfo(url);
        }

        // 判断是否登录，请求中添加登录信息
//        if (params != null && UserInfoManager.getInstance().isLogin()) {
//            params.put(ParserField.FORMHASH, UserInfoManager.getInstance().getFormHash());
//        }

        doHttpClient(url, params, method);
    }

    private void doHttpClient(String url, HashMap<String, String> params, int method) {

        String requestUrl = url;
        HttpRequestBase httpRequest = null;

        if (method == CONNECT_TYPE_HTTPCLIENT_GET) {

            if (params != null && !params.isEmpty()) {

                String paramStr = "";

                try {

                    Iterator<Entry<String, String>> iter = params.entrySet().iterator();

                    while (iter.hasNext()) {

                        Entry<String, String> entry = iter.next();
                        String key = entry.getKey();
                        if (key != null) {
                            key = URLEncoder.encode(key, "UTF-8");
                        }

                        String val = entry.getValue();

                        if (val != null) {
                            val = URLEncoder.encode(val, "UTF-8");
                        }

                        paramStr += paramStr = "&" + key + "=" + val;
                    }
                } catch (Exception e) {
                    /*
                     * exception throwed by URLEncoder.encode() -if the specified encoding scheme is
                     * invalid.
                     */
                    e.printStackTrace();
                    if (mResponedCallback != null) {
                        mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_CLIENT_ERROR, 0, null);
                    }

                    return;
                }

                if (!TextUtils.isEmpty(paramStr)) {

                    if (!url.contains("?")) {
                        paramStr = paramStr.replaceFirst("&", "?");
                    }

                    Log.d(TAG, "url " + url + " paramStr " + paramStr);
                    url += paramStr;
                }

            }

            try {
                httpRequest = new HttpGet(url);
                Log.i(TAG, "doGet the url after encode is " + url);
            } catch (IllegalArgumentException e) {
                /*
                 * throwed by new HttpGet(); if the uri is invalid.
                 */
                e.printStackTrace();
                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_CLIENT_ERROR, 0, null);
                }
                return;
            }
        } else if (method == CONNECT_TYPE_HTTPCLIENT_POST) {

            /* 组织HttpPost的参数 */
            List<NameValuePair> httpParams = null;
            if (params != null) {

                httpParams = new ArrayList<NameValuePair>();
                Iterator<Entry<String, String>> iter = params.entrySet().iterator();

                while (iter.hasNext()) {

                    Entry<String, String> entry = iter.next();
                    String key = entry.getKey();
                    String val = entry.getValue();
                    if (val == null) {
                        val = "";
                    }

                    if (val.equals(KEY_MULTI_VALUES) && mMultiKeyValues != null) {
                        for (String values : mMultiKeyValues) {
                            httpParams.add(new BasicNameValuePair(key, values));
                        }
                    } else {
                        httpParams.add(new BasicNameValuePair(key, val));
                    }
                }
            }
            
            /* 添加请求参数到请求对象 */
            try {
                httpRequest = new HttpPost(url);
            } catch (IllegalArgumentException e) {
                /*
                 * throwed by new HttpPost(); if the uri is invalid.
                 */
                e.printStackTrace();
                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_CLIENT_ERROR, 0, null);
                }

                return;
            }

            try {
                if (httpParams != null) {
                    Log.i(TAG, "url = " + url + ", httpParams = " + httpParams.toString());
                    ((HttpPost) httpRequest).setEntity(new UrlEncodedFormEntity(httpParams, HTTP.UTF_8));
                }
            } catch (Exception e) {
                /*
                 * exception throwed by URLEncoder.encode() -if the specified encoding scheme is
                 * invalid.
                 */
                e.printStackTrace();

                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_CLIENT_ERROR, 0, null);
                }

                return;
            }
        } else {
            Log.e(TAG, "doHttpClient, unspport connect type: " + method);
            return;
        }

        HttpClient httpClient = mHttpClient;

        if (mDisconnected || httpClient == null) {

            if (mResponedCallback != null) {
                mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_CONNECTION_DISCONNECTION, 0, null);
            }

            return;
        }

        ConnectivityManager connectivityManager = (ConnectivityManager) mContext
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkinfo = connectivityManager.getActiveNetworkInfo();
        if (networkinfo == null) {
            if (mResponedCallback != null) {
                mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NETWORK_ERROR, 0, null);
                Log.i(TAG, "connect failed");
            }

            return;
        }

        if ((networkinfo.getType() == ConnectivityManager.TYPE_MOBILE)) {

            String host = android.net.Proxy.getDefaultHost();
            int port = android.net.Proxy.getDefaultPort();

            if (host != null && port != -1) {

                Log.d(TAG, "Proxy host: is " + host + " port is: " + port);
                HttpHost proxy = new HttpHost(host, port, "http");
                httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
                HttpClientParams.setRedirecting(httpClient.getParams(), true);
            }
        }

        GZIPInputStream gIs = null;
        InputStream inputStream = null;
        HttpResponse httpResponse = null;
        String inputSteamString = null;

        httpRequest.addHeader("accept-encoding", "gzip");

        // 添加登录信息参数
//        String cookies = UserInfoManager.getInstance().getCookies();
//        if (!TextUtils.isEmpty(cookies)) {
//            httpRequest.addHeader(RequestParams.COOKIES_HEADER, cookies);
//        }
//
//        String formHash = UserInfoManager.getInstance().getFormHash();
//        if (!TextUtils.isEmpty(formHash)) {
//            httpRequest.addHeader(RequestParams.FORM_HASH, formHash);
//        }

        try {
            /* 发送请求并等待响应 */
            httpResponse = httpClient.execute(httpRequest);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
            httpRequest.abort();
            Log.e(TAG, "doHttpClientConnect, can't connect because ClientProtocolException");

            if (mResponedCallback != null) {
                mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_CLIENT_ERROR, 0, null);
            }

            return;
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
            httpRequest.abort();
            Log.e(TAG, "doHttpClientConnect, can't connect because IllegalArgumentException");

            if (mResponedCallback != null) {
                mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NETWORK_ERROR, 0, null);
            }

            return;
        } catch (IOException e) {
            Log.e(TAG, "doHttpClientConnect, get a IOException when connect to server");
            e.printStackTrace();
            httpRequest.abort();

            if (mTryNum >= 1) {
                mTryNum--;
                doHttpClient(requestUrl, params, method);
                return;
            } else {
                Log.e(TAG, "doHttpClientConnect, can't connect because IOException");

                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NETWORK_ERROR, 0, null);
                }

                return;
            }
        } catch (IllegalStateException e) {
            Log.e(TAG, "doHttpClientConnect, get a IllegalStateException when connect to server");
            e.printStackTrace();
            httpRequest.abort();

            if (mTryNum >= 1) {
                mTryNum--;
                doHttpClient(requestUrl, params, method);
                return;
            } else {
                Log.e(TAG, "doHttpClientConnect, can't connect because IllegalStateException");

                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NETWORK_ERROR, 0, null);
                }

                return;
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "doHttpClientConnect, get a NullPointerException when connect to server");
            e.printStackTrace();
            httpRequest.abort();

            if (mTryNum >= 1) {
                mTryNum--;
                doHttpClient(requestUrl, params, method);
                return;
            } else {
                Log.e(TAG, "doHttpClientConnect, can't connect because NullPointerException");
                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NETWORK_ERROR, 0, null);
                }

                return;
            }
        }

        if (httpResponse == null) {

            httpRequest.abort();
            Log.e(TAG, "doHttpClientConnect, got httpRespones is null");
            if (mTryNum >= 1) {
                mTryNum--;
                doHttpClient(requestUrl, params, method);
                return;
            } else {
                Log.e(TAG, "can not connect to server because get HttpRespones always null");
                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NO_RESPONSE, 0, null);
                }

                return;
            }
        }

        StatusLine statusLine = httpResponse.getStatusLine();
        if (statusLine == null) {
            httpRequest.abort();
            Log.e(TAG, "doHttpClientConnect, get status line is null");

            if (mTryNum >= 1) {
                mTryNum--;
                doHttpClient(requestUrl, params, method);
                return;
            } else {
                Log.e(TAG, "can not get the status code");

                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_NO_STATUS_CODE, 0, null);
                }

                return;
            }
        }

        int ret = statusLine.getStatusCode();
        /* 若状态码为200 ok */
        if (ret == HttpURLConnection.HTTP_OK) {
            
            /* 读返回数据 */
            HttpEntity httpEntity = httpResponse.getEntity();

            if (httpEntity != null) {

                BufferedReader buffer = null;
                InputStreamReader reader = null;
                try {

                    boolean isGzip = false;
                    Header header = httpResponse.getFirstHeader("Content-Encoding");
                    if (header != null && header.getValue().toLowerCase().equals("gzip")) {
                        isGzip = true;
                    }
                    Log.d(TAG, "isGzip " + isGzip);

                    inputStream = httpEntity.getContent();

                    if (isGzip) {
                        reader = new InputStreamReader(new GZIPInputStream(inputStream));
                    } else {
                        reader = new InputStreamReader(inputStream);
                    }

                    buffer = new BufferedReader(reader);
                    StringBuilder builder = new StringBuilder();
                    String data = "";
                    while ((data = buffer.readLine()) != null) {
                        try {
                            builder.append(data);
                        } catch (OutOfMemoryError e) {
                            builder = null;
                            break;
                        }
                    }

                    if (builder != null) {
                        inputSteamString = builder.toString();
                    }

                } catch (IllegalStateException e) {

                    httpRequest.abort();
                    Log.e(TAG,
                            "doHttpClientConnect, " + "get an IllegalStateException when get content from HttpResonse");
                    if (mTryNum >= 1) {
                        mTryNum--;
                        doHttpClient(requestUrl, params, method);
                        return;
                    } else {
                        Log.e(TAG,
                                "doHttpClientConnect, can not get content from HttpResponse because IllegalStateException");

                        if (mResponedCallback != null) {
                            mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_GET_CONTENT_ERROR, 0,
                                    null);
                        }

                        return;
                    }
                } catch (IOException e) {

                    httpRequest.abort();
                    e.printStackTrace();
                    Log.e(TAG, "doHttpClientConnect, get an IOException when get content from HttpResponse");

                    if (mTryNum >= 1) {
                        mTryNum--;
                        doHttpClient(requestUrl, params, method);
                        return;
                    } else {
                        Log.e(TAG,
                                "doHttpClientConnect, can not get content from HttpResponse because IOException");

                        if (mResponedCallback != null) {
                            mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_GET_CONTENT_ERROR, 0,
                                    null);
                        }

                        return;
                    }
                } finally {

                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    if (gIs != null) {
                        try {
                            gIs.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    if (buffer != null) {
                        try {
                            buffer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    if (reader != null) {
                        try {
                            reader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } else {

            httpRequest.abort();
            Log.e(TAG, "doHttpClientConnect, get wrong status code : " + ret);

            if (mTryNum >= 1) {
                mTryNum--;
                doHttpClient(requestUrl, params, method);
                return;
            } else {
                Log.e(TAG, "doHttpClientConnect, status code error, status code is " + ret);

                if (mResponedCallback != null) {
                    mResponedCallback.response(this, NetConnectResponse.CONNECT_FAILED_WRONG_STATUS_CODE, ret, null);
                }

                return;
            }
        }

        if (mResponedCallback != null) {
            mResponedCallback.response(this, NetConnectResponse.CONNECT_SUCCESS, 0, inputSteamString);
        }

        httpRequest.abort();
    }

    /**
     * append cellphone's params
     */
    public interface AppendInfo {

        public String onAppendRequestInfo(String url);

    }
}
